/*
 * Copyright (c) 2024 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
#include <stdint.h>

#include <cassert>
#include <cstring>
#include <string>
#include <vector>

#include "common-interop.h"

using namespace std;

// Must be synced with "enum RuntimeType" in TS.
enum RuntimeType
{
  RUNTIME_UNEXPECTED = -1,
  RUNTIME_NUMBER = 1,
  RUNTIME_STRING = 2,
  RUNTIME_OBJECT = 3,
  RUNTIME_BOOLEAN = 4,
  RUNTIME_UNDEFINED = 5,
  RUNTIME_BIGINT = 6,
  RUNTIME_FUNCTION = 7,
  RUNTIME_SYMBOL = 8
};

enum Tags
{
  TAG_UNDEFINED = 101,
  TAG_INT32 = 102,
  TAG_FLOAT32 = 103,
  TAG_STRING = 104,
  TAG_LENGTH = 105,
  TAG_RESOURCE = 106,
  TAG_OBJECT = 107,
};

typedef float float32_t;

template <typename T>
inline void WriteToString(string* result, const T& value) = delete;

template <typename T>
struct Tagged
{
  Tags tag;
  T value;
};

inline const char* tagName(Tags tag) {
  switch (tag) {
    case Tags::TAG_UNDEFINED: return "UNDEFINED";
    case Tags::TAG_INT32: return "INT32";
    case Tags::TAG_FLOAT32: return "FLOAT32";
    case Tags::TAG_LENGTH: return "LENGTH";
    case Tags::TAG_RESOURCE: return "RESOURCE";
    case Tags::TAG_STRING: return "STRING";
    case Tags::TAG_OBJECT: return "OBJECT";
  }
  fprintf(stderr, "tag name %d is wrong\n", tag);
  throw "Error";
}

template <typename T>
inline void WriteToString(string* result, const Tagged<T>& value) {
    result->append("tagged {[");
    result->append(tagName(value.tag));
    result->append("]");
    if (value.tag != TAG_UNDEFINED) {
      WriteToString(result, value.value);
    }
    result->append("}");
}

template <typename T>
inline void addToString(string* result, const T& value, bool needComma = false) {
  WriteToString(result, value);
  if (needComma) result->append(", ");
}

template <>
inline void WriteToString(string* result, const KBoolean& value) {
  result->append(std::to_string(value));
}

template <>
inline void WriteToString(string* result, const KInt& value) {
  result->append(std::to_string(value));
}


struct Empty
{
};

inline void WriteToString(string* result, const Empty& value) {
}

template <>
inline void addToString(string* result, const Empty& value, bool needComma) {
}

struct Error
{
  std::string message;
  Error(const std::string &message) : message(message) {}
};

template <typename T0, typename T1 = Empty, typename T2 = Empty, typename T3 = Empty, typename T4 = Empty, typename T5 = Empty, typename T6 = Empty>
struct Union
{
  Union() : selector(-1) {}
  Union(int32_t selector) : selector(selector) {}
  Union& operator=(const Union& other) {
    this->selector = other.selector;
    switch (selector)
    {
    case 0:
      this->value0 = other.value0;
      break;
    case 1:
      this->value1 = other.value1;
      break;
    case 2:
      this->value2 = other.value2;
      break;
    case 3:
      this->value3 = other.value3;
      break;
    case 4:
      this->value4 = other.value4;
      break;
    case 5:
      this->value5 = other.value5;
      break;
    case 6:
      this->value6 = other.value6;
      break;
    }
    return *this;
  }
  Union(const Union<T0, T1, T2, T3, T4, T5, T6> &other)
  {
    this->selector = other.selector;
    switch (selector)
    {
    case 0:
      this->value0 = other.value0;
      break;
    case 1:
      this->value1 = other.value1;
      break;
    case 2:
      this->value2 = other.value2;
      break;
    case 3:
      this->value3 = other.value3;
      break;
    case 4:
      this->value4 = other.value4;
      break;
    case 5:
      this->value5 = other.value5;
      break;
    case 6:
      this->value6 = other.value6;
      break;
    }
  }
  /*
  Union& operator=(Union&& other)
  {
    this->selector = other.selector;
    switch (selector)
    {
    case 0:
      this->value0 = std::move(other.value0);
      break;
    case 1:
      this->value1 = std::move(other.value1);
      break;
    case 2:
      this->value2 = std::move(other.value2);
      break;
    case 3:
      this->value3 = std::move(other.value3);
      break;
    case 4:
      this->value4 = std::move(other.value4);
      break;
    case 5:
      this->value5 = std::move(other.value5);
      break;
    case 6:
      this->value6 = std::move(other.value6);
      break;
    }
    return *this;
  } */
  ~Union() {
    switch (selector)
    {
    case 0:
      this->value0.~T0();
      break;
    case 1:
      this->value1.~T1();
      break;
    case 2:
      this->value2.~T2();
      break;
    case 3:
      this->value3.~T3();
      break;
    case 4:
      this->value4.~T4();
      break;
    case 5:
      this->value5.~T5();
      break;
    case 6:
      this->value6.~T6();
      break;
    }
  }
  int32_t selector;
  union
  {
    T0 value0;
    T1 value1;
    T2 value2;
    T3 value3;
    T4 value4;
    T5 value5;
    T6 value6;
  };
};

template <typename T0, typename T1, typename T2, typename T3, typename T4, typename T5, typename T6>
inline void WriteToString(string* result, const Union<T0, T1, T2, T3, T4, T5, T6>& value) {
    result->append("union[");
    result->append(std::to_string(value.selector) + "] {");
    switch (value.selector)
    {
    case 0:
      addToString(result, value.value0);
      break;
    case 1:
      addToString(result, value.value1);
      break;
    case 2:
      addToString(result, value.value2);
      break;
    case 3:
      addToString(result, value.value3);
      break;
    case 4:
      addToString(result, value.value4);
      break;
    case 5:
      addToString(result, value.value5);
      break;
    case 6:
      addToString(result, value.value6);
      break;
    }
    result->append("}");
}

template <typename T0 = Empty, typename T1 = Empty, typename T2 = Empty, typename T3 = Empty, typename T4 = Empty, typename T5 = Empty>
struct Compound
{
  Compound() {}
  Compound(const Compound<T0, T1, T2, T3, T4, T5> &other)
  {
    this->value0 = other.value0;
    this->value1 = other.value1;
    this->value2 = other.value2;
    this->value3 = other.value3;
    this->value4 = other.value4;
    this->value5 = other.value5;
  }
  ~Compound() {}
  Compound& operator=(const Compound& other) {
    this->value0 = other.value0;
    this->value1 = other.value1;
    this->value2 = other.value2;
    this->value3 = other.value3;
    this->value4 = other.value4;
    this->value5 = other.value5;
    return *this;
  }
  /*
  Compound& operator=(Compound&& other) {
    this->value0 = std::move(other.value0);
    this->value1 = std::move(other.value1);
    this->value2 = std::move(other.value2);
    this->value3 = std::move(other.value3);
    this->value4 = std::move(other.value4);
    this->value5 = std::move(other.value5);
    return *this;
  } */
  T0 value0;
  T1 value1;
  T2 value2;
  T3 value3;
  T4 value4;
  T5 value5;
};

template <typename T0, typename T1, typename T2, typename T3, typename T4, typename T5>
inline void WriteToString(string* result, const Compound<T0, T1, T2, T3, T4, T5>& value) {
  result->append("compound: {");
  addToString(result, value.value0, true);
  addToString(result, value.value1, true);
  addToString(result, value.value2, true);
  addToString(result, value.value3, true);
  addToString(result, value.value4, true);
  addToString(result, value.value5, false);
  result->append("}");
}

struct Number
{
  // TODO: shall we keep a tag here?
  Number() {}
  Number(KInt value): i32(value) {}
  Number(KFloat value): f32(value) {}
  Number(const Tagged<Number> &other)
  {
    // TODO: check tag
    this->i32 = other.value.i32;
  }
  Number(const Number &other)
  {
    this->i32 = other.i32;
  }

  ~Number() {}
  union
  {
    float32_t f32;
    int32_t i32;
  };

};

template <>
inline void WriteToString(string* result, const Number& value) {
  *result += std::to_string(value.i32);
}

struct String
{
  String() {}
  String(const std::string &value) : value(value) {}
  String(const KStringPtr& value) : value(value.c_str(), value.length()) {}

  String(const String &other)
  {
    this->value = other.value;
  }
  String(const Tagged<String> &other)
  {
    // TODO: check    tag
    this->value = other.value.value;
  }
  ~String() {}
  std::string value;
  std::string toString() const {
    return "\"" + value + "\"";
  }
  const char* c_str() const {
    return value.c_str();
  }
};

template <>
inline void WriteToString(string* result, const String& value) {
  *result += value.toString();
}

template <typename T>
struct Array
{
  std::vector<T> array;
  size_t size() const { return array.size(); }
  void push_back(const T& value) { array.push_back(value); }
  const T& operator[](size_t pos ) const { return array[pos]; }
  T& operator[](size_t pos) { return array[pos]; }
  void resize(size_t size) { array.resize(size); }
};

template <typename T>
inline void WriteToString(string* result, const Array<T>& value) {
  result->append("[");
  for (int i = 0; i < value.size(); i++) {
        addToString(result, value[i], i != value.size() - 1);
  }
  result->append("]");
}

struct Length
{
  float32_t value;
  int32_t unit;
  int32_t resource;
  Length() : value(0), unit(0), resource(0) {}
  Length(const Length &other) : value(other.value), unit(other.unit), resource(other.resource) {}
  Length(const Tagged<Length> &other)
  {
    // TODO: check tag
    this->value = other.value.value;
    this->unit = other.value.unit;
    this->resource = other.value.resource;
  }
  Length(const int32_t *array)
  {
    this->value = *(float32_t *)array;
    this->unit = array[1];
    this->resource = array[2];
  }

  ~Length() {}

  static Length fromArray(int32_t *array)
  {
    Length result;
    result.value = *(float32_t *)array;
    result.unit = array[1];
    result.resource = array[2];
    return result;
  }
};

inline const char* getUnitName(int value) {
  switch (value) {
    case 0: return "px";
    case 1: return "vp";
    case 3: return "%";
    case 4: return "lpx";
    default: return "<unknown>";
  }
}

template <>
inline void WriteToString(string* result, const Length& value) {
  result->append("Length {");
  result->append("value=");
  result->append(std::to_string(value.value));
  result->append(", unit=" + string(getUnitName(value.unit)));
  result->append(", resource=" + std::to_string(value.resource));
  result->append("}");
}

struct Undefined
{
  int8_t bogus;
  string toString() {
    return "undefined";
  }
};
template <>
inline void WriteToString(string* result, const Undefined& value) {
  result->append("undefined");
}

class ArgDeserializerBase;

struct CustomObject {
  char kind[20];
  CustomObject(): id(0) {
    kind[0] = 0;
  }
  CustomObject(const string& kind): id(0) {
    strncpy(this->kind, kind.c_str(), sizeof(this->kind));
  }
  CustomObject(const CustomObject& other) {
    strncpy(this->kind, other.kind, sizeof(this->kind));
    this->id = other.id;
    // TODO: copy data.
  }
  ~CustomObject() {}
  CustomObject& operator=(CustomObject&& other) {
    this->id = other.id;
    strncpy(this->kind, other.kind, sizeof(this->kind));
    // TODO: copy data.
    return *this;
  }
  CustomObject& operator=(const CustomObject& other) {
    this->id = other.id;
    strncpy(this->kind, other.kind, sizeof(this->kind));
    // TODO: copy data.
    return *this;
  }
  int32_t id;
  // Data of custom object.
  int32_t ints[4];
  float32_t floats[4];
  void* pointers[4];
};

typedef CustomObject Function;
typedef CustomObject Callback;
typedef CustomObject ErrorCallback;
typedef CustomObject Any;

inline void WriteToString(string* result, const CustomObject& value) {
  result->append("Custom kind=");
  result->append(value.kind);
  result->append(" id=");
  result->append(std::to_string(value.id));
}

struct CustomDeserializer {
  virtual bool supports(const string& kind) { return false; }
  virtual CustomObject deserialize(ArgDeserializerBase* deserializer, const string& kind) {
    return CustomObject("error");
  }
  CustomDeserializer* next = nullptr;
};

class ArgDeserializerBase
{
protected:
  uint8_t *data;
  int32_t length;
  int32_t position;

  static CustomDeserializer* customDeserializers;
public:
  ArgDeserializerBase(uint8_t *data, int32_t length)
      : data(data), length(length), position(0) {}

  static void registerCustomDeserializer(CustomDeserializer* deserializer) {
    if (ArgDeserializerBase::customDeserializers == nullptr) {
      ArgDeserializerBase::customDeserializers = deserializer;
    } else {
      auto* current = ArgDeserializerBase::customDeserializers;
      while (current->next != nullptr) current = current->next;
      current->next = deserializer;
    }
  }

  int32_t currentPosition() const { return this->position; }

  void check(int32_t count)
  {
    if (position + count > length)
    {
      assert(false);
    }
  }

  CustomObject readCustom(string kind) {
      auto* current = ArgDeserializerBase::customDeserializers;
      while (current) {
        if (current->supports(kind)) {
          return current->deserialize(this, kind);
        }
      }
      fprintf(stderr, "Unsupported custom deserialization for %s\n", kind.c_str());
      int tag = readInt8();
      assert(tag == TAG_UNDEFINED);
      // Skip updefined tag!.
      return CustomObject(string("Error") + kind);
  }

  int8_t readInt8()
  {
    check(1);
    auto value = *(data + position);
    position += 1;
    return value;
  }
  bool readBoolean()
  {
    check(1);
    auto value = *(data + position);
    position += 1;
    return value;
  }
  int32_t readInt32()
  {
    check(4);
    auto value = *(int32_t *)(data + position);
    position += 4;
    return value;
  }
  float32_t readFloat32()
  {
    check(4);
    auto value = *(float32_t *)(data + position);
    position += 4;
    return value;
  }
  Tagged<Number> readNumber()
  {
    check(5);
    Tagged<Number> result;
    result.tag = (Tags)readInt8();
    if (result.tag == Tags::TAG_INT32)
    {
      result.value.i32 = readInt32();
    }
    else if (result.tag == Tags::TAG_FLOAT32)
    {
      result.value.f32 = readFloat32();
    } else {
      fprintf(stderr, "Bad number tag %d\n", result.tag);
      throw "Unknown number tag";
    }
    return result;
  }

  Tagged<Length> readLength()
  {
    Tagged<Length> result;
    result.tag = (Tags)readInt8();
    if (result.tag == Tags::TAG_LENGTH) {
      result.value.value = readFloat32();
      result.value.unit = readInt32();
      result.value.resource = readInt32();
    } else if (result.tag == Tags::TAG_UNDEFINED) {
    } else {
      fprintf(stderr, "Bad length tag %d\n", result.tag);
      throw "Error";
    }
    return result;
  }

  Compound<Number, Number> readAnimationRange()
  {
    Compound<Number, Number> result;
    result.value0 = readNumber();
    result.value1 = readNumber();
    return result;
  }

  Undefined readUndefined()
  {
    return Undefined();
  }

  Tagged<String> readString()
  {
    Tagged<String> result;
    result.tag = (Tags)readInt8();
    if (result.tag == Tags::TAG_UNDEFINED) {
      return result;
    }
    if (result.tag != Tags::TAG_STRING) {
      fprintf(stderr, "Unexpected string tag: %d\n", result.tag);
      throw "Error";
    }
    int32_t length = readInt32();
    check(length);
    result.value = std::string((char *)(data + position), length);
    position += length;
    return result;
  }

  Function readFunction()
  {
    return readCustom("Function");
  }

  Callback readCallback()
  {
    return readFunction();
  }

  ErrorCallback readErrorCallback()
  {
    return readFunction();
  }

};